package org.hackystat.ui.projectviewer.gwt.server.servlet;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TreeSet;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.datatype.XMLGregorianCalendar;

import org.hackystat.telemetry.service.client.TelemetryClient;
import org.hackystat.telemetry.service.client.TelemetryClientException;
import org.hackystat.telemetry.service.resource.chart.jaxb.TelemetryChartData;
import org.hackystat.ui.projectviewer.gwt.server.etl.Series;
import org.hackystat.ui.projectviewer.gwt.server.etl.SeriesDataSet;
import org.hackystat.utilities.tstamp.Tstamp;

/**
 * Facilitates data download.
 *
 * @author Pavel Senin.
 *
 */
public class FetchFileServlet extends HttpServlet {

  /* Fancy Eclipse serial. */
  private static final long serialVersionUID = 7384463339221822000L;

  private static final String CR = "\n";
  private static final String DL = ",";
  private static final String ST = "\"";

  private static final String unitTestChartDef = "UnitTest";
  private static final String devTimeChartDef = "DevTime";
  private static final String coverageChartDef = "Coverage";
  private static final String commitChartDef = "Commit";
  private static final String buildChartDef = "Build";
  private static final String codeIssueChartDef = "CodeIssue";
  private static final String fileMetricChartDef = "FileMetric";

  private static final String DAY = "Day";

  // /** Logger. */
  // private static final Logger logger = HackystatLogger
  // .getLogger("org.hackystat.ui.projectviewer.gwt.projectviewer-fileservlet");

  // /**
  // * Constructor.
  // */
  // public FetchFileServlet() {
  // super();
  // // getting main logger instance
  // // logger.setLevel(Level.ALL);
  // // logger.info("Starting file fetch servlet.");
  // }

  /**
   * {@inheritDoc}
   */
  public void doGet(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {

    SimpleDateFormat dtf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.US);
    byte[] bbuffer = new byte[1024];

    String path = getServletConfig().getServletContext().getRealPath("/");
    String filename = path + "/test1.txt";

    try {
      // retrieving and checking parameters for data extraction first
      String prjId = request.getParameter("project");
      String prjOwner = request.getParameter("owner");
      String sdtType = request.getParameter("sdt");
      String host = request.getParameter("host");
      String user = request.getParameter("user");
      String password = request.getParameter("password");
      String startDate = request.getParameter("startDate");
      String endDate = request.getParameter("endDate");
      if ((null == prjId) || (prjId.length() == 0)) {
        return;
      }
      else if ((null == prjOwner) || (prjOwner.length() == 0)) {
        return;
      }
      else if ((null == sdtType) || (sdtType.length() == 0)) {
        return;
      }
      else if ((null == host) || (host.length() == 0)) {
        return;
      }
      else if ((null == user) || (user.length() == 0)) {
        return;
      }
      else if ((null == password) || (password.length() == 0)) {
        return;
      }
      else if ((null == startDate) || (startDate.length() == 0)) {
        return;
      }
      else if ((null == endDate) || (endDate.length() == 0)) {
        return;
      }
      else {
        // if everything looks OK - extracting dates
        Date stDate = dtf.parse(startDate);
        XMLGregorianCalendar startTstamp = Tstamp.makeTimestamp(stDate.getTime());
        Date edDate = dtf.parse(endDate);
        XMLGregorianCalendar endTstamp = Tstamp.makeTimestamp(edDate.getTime());
        // debuggy
        System.out.println(" <><> debuggy: FileFetch servlet understood your request "
            + "and processing with parameters: prj:" + prjId + " ,owner: " + prjOwner + ", start:"
            + dtf.format(startTstamp.toGregorianCalendar().getTime()) + ", end:"
            + dtf.format(endTstamp.toGregorianCalendar().getTime()) + ", sdt:" + sdtType
            + ", host:" + host + ", user:" + user + ", pass:" + password + ". Filename: "
            + filename);

        // open file writer and telemetry client
        BufferedWriter out = new BufferedWriter(new FileWriter(new File(filename)));

        SeriesDataSet sds = null;

        // now, extract and populate data to the file
        if (sdtType.equalsIgnoreCase("UnitTest")) {
          sds = extractUnitTestSeries(host, user, password, prjId, prjOwner, startTstamp,
              endTstamp);
        }
        else if (sdtType.equalsIgnoreCase("DevEvent")) {
          sds = extractDevTimeSeries(host, user, password, prjId, prjOwner, startTstamp, endTstamp);
        }
        else if (sdtType.equalsIgnoreCase("Coverage")) {
          sds = extractCoverageTimeSeries(host, user, password, prjId, prjOwner, startTstamp,
              endTstamp);
        }
        else if (sdtType.equalsIgnoreCase("Commit")) {
          sds = extractCommitTimeSeries(host, user, password, prjId, prjOwner, startTstamp,
              endTstamp);
        }
        else if (sdtType.equalsIgnoreCase("Build")) {
          sds = extractBuildTimeSeries(host, user, password, prjId, prjOwner, startTstamp,
              endTstamp);
        }
        else if (sdtType.equalsIgnoreCase("CodeIssue")) {
          sds = extractCodeIssueTimeSeries(host, user, password, prjId, prjOwner, startTstamp,
              endTstamp);
        }
        else if (sdtType.equalsIgnoreCase("FileMetric")) {
          sds = extractFileMetricTimeSeries(host, user, password, prjId, prjOwner, startTstamp,
              endTstamp);
        }
        if (null == sds) {
          out.write("FileFetch servlet understood your request but unable to process "
              + "with data retrieval");
        }
        else {
          writeSeries(sds, out);
        }

        out.flush();
        out.close();

        // <><><><><><><><><><><><><><><><><
        File file = new File(filename);
        FileInputStream fis = new FileInputStream(filename);

        // logger.info("FileFetch servlet: File path: " + filename);

        // response.reset();
        ServletOutputStream servletOutputStream = response.getOutputStream();

        // ServletContext context = getServletConfig().getServletContext();
        String mimetype = "text/plain";
        // String mimetype = context.getMimeType(filename);
        // logger.info("OMDB: MIME TYPE: " + mimetype);

        response.setContentType((mimetype != null) ? mimetype : "text/plain");

        response.setContentLength((int) file.length());
        response.setHeader("Content-Disposition", "attachement; filename=\"" + filename + "\"");

        int bytes;
        while ((bytes = fis.read(bbuffer)) != -1) {
          servletOutputStream.write(bbuffer, 0, bytes);
        }

        // logger.info(": File " + filename + " sent.");

        fis.close();
        servletOutputStream.flush();
        servletOutputStream.close();

      }
    }
    catch (TelemetryClientException e) {
      e.printStackTrace();
    }
    catch (ParseException e) {
      e.printStackTrace();
    }
  }

  /**
   * Retrieves the Build telemetry data from telemetry service.
   *
   * @param host The telemetry host.
   * @param user The user name to use.
   * @param password The user password.
   * @param prjId The projectId.
   * @param prjOwner The project owner.
   * @param startTstamp The period begin.
   * @param endTstamp The period end.
   * @return SeriesDataSet for
   * @throws TelemetryClientException if an error encountered.
   */
  private SeriesDataSet extractBuildTimeSeries(String host, String user, String password,
      String prjId, String prjOwner, XMLGregorianCalendar startTstamp,
      XMLGregorianCalendar endTstamp) throws TelemetryClientException {

    SeriesDataSet res = new SeriesDataSet();

    // opening connection.
    TelemetryClient client = new TelemetryClient(host, user, password);
    client.authenticate();

    // making up parameters
    //
    // <ParameterDefinition Name="member">
    // <Type Default="*" Name="Text"/>
    // <ParameterDefinition Name="result">
    // <Type Default="*" Name="Enumerated">
    // <Value>Success</Value><Value>Failure</Value><Value>*</Value></Type>
    // <ParameterDefinition Name="type">
    // <Type Default="*" Name="Text"/>
    // <Description>
    // The value of the Type property to match in order for the Build data to be counted. This is
    // useful, for example, for generating Build telemetry for only Integration Builds.
    // The default returns Build data for any type.
    // </Description>
    // <ParameterDefinition Name="cumulative">
    // <Type Default="false" Name="Boolean"/>
    // <SourceCode>
    // chart Build(member, result, type, cumulative) = {"Build invocations",
    // (BuildStream(member, result, type, cumulative), yAxis("Build Count"))};
    String params = "";
    // total successful tests
    params = user + ",Success,*,false";
    TelemetryChartData totalSuccess = client.getChart(buildChartDef, prjOwner, prjId, DAY,
        startTstamp, endTstamp, params);
    res.put("SuccessCount", totalSuccess.getTelemetryStream().get(0));
    // total failed tests
    params = user + ",Failure,*,false";
    TelemetryChartData totalFailure = client.getChart(buildChartDef, prjOwner, prjId, DAY,
        startTstamp, endTstamp, params);
    res.put("FailureCount", totalFailure.getTelemetryStream().get(0));

    return res;
  }

  /**
   * Retrieves the UnitTest telemetry data from telemetry service.
   *
   * @param host The telemetry host.
   * @param user The user name to use.
   * @param password The user password.
   * @param prjId The projectId.
   * @param prjOwner The project owner.
   * @param startTstamp The period begin.
   * @param endTstamp The period end.
   * @return SeriesDataSet for
   * @throws TelemetryClientException if an error encountered.
   */
  private SeriesDataSet extractUnitTestSeries(String host, String user, String password,
      String prjId, String prjOwner, XMLGregorianCalendar startTstamp,
      XMLGregorianCalendar endTstamp) throws TelemetryClientException {

    SeriesDataSet res = new SeriesDataSet();

    // opening connection.
    TelemetryClient client = new TelemetryClient(host, user, password);
    client.authenticate();

    // making up parameters
    //
    // <ParameterDefinition Name="mode">
    // <Type Default="TotalCount" Name="Enumerated">
    // <Value>TotalCount</Value><Value>SuccessCount</Value><Value>FailureCount</Value>
    // <ParameterDefinition Name="member">
    // <Type Default="*" Name="Text"/>
    // <ParameterDefinition Name="cumulative">
    // <Type Default="false" Name="Boolean"/>
    // <SourceCode>
    // chart UnitTest(mode, member, cumulative) = { "Unit test invocations",
    // (UnitTestStream(mode, member, cumulative), yAxis("UnitTest Count"))};

    String params = "";
    // total successful tests
    params = "SuccessCount," + user + ",false";
    TelemetryChartData totalSuccess = client.getChart(unitTestChartDef, prjOwner, prjId, DAY,
        startTstamp, endTstamp, params);
    res.put("SuccessCount", totalSuccess.getTelemetryStream().get(0));
    // total failed tests
    params = "FailureCount,*,false";
    TelemetryChartData totalFailure = client.getChart(unitTestChartDef, user, prjId, DAY,
        startTstamp, endTstamp, params);
    res.put("FailureCount", totalFailure.getTelemetryStream().get(0));

    return res;
  }

  /**
   * Retrieves the DevTime telemetry data from telemetry service.
   *
   * @param host The telemetry host.
   * @param user The user name to use.
   * @param password The user password.
   * @param prjId The projectId.
   * @param prjOwner The project owner.
   * @param startTstamp The period begin.
   * @param endTstamp The period end.
   * @return SeriesDataSet for
   * @throws TelemetryClientException if an error encountered.
   */
  private SeriesDataSet extractDevTimeSeries(String host, String user, String password,
      String prjId, String prjOwner, XMLGregorianCalendar startTstamp,
      XMLGregorianCalendar endTstamp) throws TelemetryClientException {

    SeriesDataSet res = new SeriesDataSet();

    // SimpleDateFormat dtf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.US);

    // opening connection.
    TelemetryClient client = new TelemetryClient(host, user, password);
    client.authenticate();

    // making up parameters
    //
    // <ParameterDefinition Name="member">
    // <Type Default="*" Name="Text"/>
    // </ParameterDefinition>
    // <ParameterDefinition Name="cumulative">
    // <Type Default="false" Name="Boolean"/>
    // </ParameterDefinition>
    // <SourceCode>
    // chart DevTime(member, cumulative) = {"Dev Time in hours",
    // (DevTimeStream(member, cumulative), yAxis("Hours")) };

    String params = "";
    // total successful tests
    params = user + ",false";
    // System.out.println(" <><> debuggy: FileFetch servlet getting DevEvent chart for: " + prjId
    // + ", start:" + dtf.format(startTstamp.toGregorianCalendar().getTime()) + ", end:"
    // + dtf.format(endTstamp.toGregorianCalendar().getTime()) + ", params:" + params);
    TelemetryChartData devTimeTotal = client.getChart(devTimeChartDef, prjOwner, prjId, DAY,
        startTstamp, endTstamp, params);
    res.put("DevTimeTotal", devTimeTotal.getTelemetryStream().get(0));

    return res;
  }

  /**
   * Retrieves the Coverage telemetry data from telemetry service.
   *
   * @param host The telemetry host.
   * @param user The user name to use.
   * @param password The user password.
   * @param prjId The projectId.
   * @param prjOwner The project owner.
   * @param startTstamp The period begin.
   * @param endTstamp The period end.
   * @return SeriesDataSet for
   * @throws TelemetryClientException if an error encountered.
   */
  private SeriesDataSet extractCoverageTimeSeries(String host, String user, String password,
      String prjId, String prjOwner, XMLGregorianCalendar startTstamp,
      XMLGregorianCalendar endTstamp) throws TelemetryClientException {

    SeriesDataSet res = new SeriesDataSet();

    // opening connection.
    TelemetryClient client = new TelemetryClient(host, user, password);
    client.authenticate();

    // making up parameters
    //
    // <ParameterDefinition Name="mode">
    // <Type Default="Percentage" Name="Enumerated">
    // <Value>Percentage</Value><Value>NumCovered</Value><Value>NumUncovered</Value>
    // </Type>
    // <ParameterDefinition Name="granularity">
    // <Type Default="method" Name="Text"/>
    // 'line', 'method', 'class', etc...
    // <SourceCode>
    // chart Coverage(mode, granularity) = {"Coverage associated with this project, mode, " +
    // "and granularity",(CoverageStream(mode, granularity), yAxis("Coverage"))};
    String params = "Percentage,line";
    // total successful tests
    TelemetryChartData coverageLineTotal = client.getChart(coverageChartDef, prjOwner, prjId, DAY,
        startTstamp, endTstamp, params);
    res.put("CoveragePercentageLine", coverageLineTotal.getTelemetryStream().get(0));

    return res;
  }

  /**
   * Retrieves the Commit telemetry data from telemetry service.
   *
   * @param host The telemetry host.
   * @param user The user name to use.
   * @param password The user password.
   * @param prjId The projectId.
   * @param prjOwner The project owner.
   * @param startTstamp The period begin.
   * @param endTstamp The period end.
   * @return SeriesDataSet for
   * @throws TelemetryClientException if an error encountered.
   */
  private SeriesDataSet extractCommitTimeSeries(String host, String user, String password,
      String prjId, String prjOwner, XMLGregorianCalendar startTstamp,
      XMLGregorianCalendar endTstamp) throws TelemetryClientException {

    SeriesDataSet res = new SeriesDataSet();

    // opening connection.
    TelemetryClient client = new TelemetryClient(host, user, password);
    client.authenticate();

    // making up parameters
    //
    // <ParameterDefinition Name="member">
    // <Type Default="*" Name="Text"/>
    // The email of the member whose Commit counts are to be returned.
    // </ParameterDefinition>
    // <ParameterDefinition Name="cumulative">
    // <Type Default="false" Name="Boolean"/>
    // </ParameterDefinition>
    // <SourceCode>
    // chart Commit(member, cumulative) = { "Commit invocations",
    // (CommitStream(member, cumulative), yAxis("Commit Count"))};
    String params = user + ",false";
    // total successful tests
    TelemetryChartData commitsTotal = client.getChart(commitChartDef, prjOwner, prjId, DAY,
        startTstamp, endTstamp, params);
    res.put("CommitsTotal", commitsTotal.getTelemetryStream().get(0));

    return res;
  }

  /**
   * Retrieves the Code Issue telemetry data from telemetry service.
   *
   * @param host The telemetry host.
   * @param user The user name to use.
   * @param password The user password.
   * @param prjId The projectId.
   * @param prjOwner The project owner.
   * @param startTstamp The period begin.
   * @param endTstamp The period end.
   * @return SeriesDataSet for
   * @throws TelemetryClientException if an error encountered.
   */
  private SeriesDataSet extractCodeIssueTimeSeries(String host, String user, String password,
      String prjId, String prjOwner, XMLGregorianCalendar startTstamp,
      XMLGregorianCalendar endTstamp) throws TelemetryClientException {

    SeriesDataSet res = new SeriesDataSet();

    // opening connection.
    TelemetryClient client = new TelemetryClient(host, user, password);
    client.authenticate();

    // making up parameters
    //
    // <ParameterDefinition Name="tool">
    // <Type Default="*" Name="Text"/>
    // <Description>
    // The tool whose CodeIssue counts are to be returned, or '*' for all tools.
    // </Description></ParameterDefinition>
    // <ParameterDefinition Name="type">
    // <Type Default="*" Name="Text"/><Description>
    // The type of CodeIssue whose counts are to be returned, or '*' for all types.
    // </Description></ParameterDefinition>
    // <SourceCode>
    // chart CodeIssue(tool, type) = {"CodeIssues for the given tool and type.",
    // (CodeIssueStream(tool, type), yAxis("CodeIssue Count"))};

    String params = "*,*";
    // total successful tests
    TelemetryChartData issuesTotal = client.getChart(codeIssueChartDef, prjOwner, prjId, DAY,
        startTstamp, endTstamp, params);
    res.put("CommitsTotal", issuesTotal.getTelemetryStream().get(0));

    return res;
  }

  /**
   * Retrieves the FileMetric telemetry data from telemetry service.
   *
   * @param host The telemetry host.
   * @param user The user name to use.
   * @param password The user password.
   * @param prjId The projectId.
   * @param prjOwner The project owner.
   * @param startTstamp The period begin.
   * @param endTstamp The period end.
   * @return SeriesDataSet for
   * @throws TelemetryClientException if an error encountered.
   */
  private SeriesDataSet extractFileMetricTimeSeries(String host, String user, String password,
      String prjId, String prjOwner, XMLGregorianCalendar startTstamp,
      XMLGregorianCalendar endTstamp) throws TelemetryClientException {

    SeriesDataSet res = new SeriesDataSet();

    // opening connection.
    TelemetryClient client = new TelemetryClient(host, user, password);
    client.authenticate();

    // making up parameters
    //
    // <ParameterDefinition Name="sizemetric">
    // <Type Default="TotalLines" Name="Text"/><Description>
    // The type of size metric to search for in the FileMetric entries.
    // </Description></ParameterDefinition>
    // <SourceCode>
    // chart FileMetric(sizemetric) = {"FileMetrics associated with this size metric.",
    // (FileMetricStream(sizemetric), yAxis(sizemetric))};

    String params = "TotalLines, *";
    // total successful tests
    TelemetryChartData linesTotal = client.getChart(fileMetricChartDef, prjOwner, prjId, DAY,
        startTstamp, endTstamp, params);
    res.put("LOCTotal", linesTotal.getTelemetryStream().get(0));

    return res;
  }

  /**
   * Takes as input the map which contains data timeseries and writes them into file.
   *
   * @param sds The map with series, must be TreeMap to guarantee iteration order.
   * @param out The output writer.
   * @throws IOException if I/O error happens.
   */
  private void writeSeries(SeriesDataSet sds, BufferedWriter out) throws IOException {

    SimpleDateFormat dtf = new SimpleDateFormat("yyyy-MM-dd", Locale.US);

    // get the time axis
    TreeSet<Date> axis = sds.getTimeAxis();

    StringBuffer sb = new StringBuffer(5000);

    // first step is to write headers
    sb.append("date,year,month,WeekOfYear,WeekOfMonth,DayOfYear,DayOfMont,DayOfWeek" + DL);
    int counter = 0;
    for (String str : sds.getSeries().keySet()) {
      sb.append(str);
      if (counter < sds.getSeries().keySet().size() - 1) {
        sb.append(DL);
      }
      counter++;
    }
    out.write(sb + CR);

    // second step - is go over all series by populating data
    for (Date date : axis) {
      Calendar gc = GregorianCalendar.getInstance(Locale.US);
      gc.setTime(date);
      sb = new StringBuffer(5000);
      // full date
      sb.append(ST + dtf.format(date) + ST + DL);
      // year
      sb.append(gc.get(Calendar.YEAR) + DL);
      // month of the year
      sb.append(gc.get(Calendar.MONTH) + DL);
      // week of the year
      sb.append(gc.get(Calendar.WEEK_OF_YEAR) + DL);
      // week of the month
      sb.append(gc.get(Calendar.WEEK_OF_MONTH) + DL);
      // day of the year
      sb.append(gc.get(Calendar.DAY_OF_YEAR) + DL);
      // day of the month
      sb.append(gc.get(Calendar.DAY_OF_MONTH) + DL);
      // day of the week
      sb.append(gc.get(Calendar.DAY_OF_WEEK) + DL);
      counter = 0;
      for (Series s : sds.getSeries().values()) {
        sb.append(s.getSeries().get(date).getValue());
        if (counter < sds.getSeries().keySet().size() - 1) {
          sb.append(DL);
        }
        counter++;
      }
      out.write(sb + CR);
    }
  }

}
